/* Copyright 2017, 2018, Gabriel Czernikier
*
* This file is part of Notehaus.
* Notehaus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* Notehaus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with Notehaus.  If not, see <http://www.gnu.org/licenses/>.
*/
#define _BSD_SOURCE
#include <endian.h>
#include <errno.h>
#include <iconv.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include "lgwseq.h"
#include "types.h"

lgwseq_t *for_lgwseq(lgwseq_t **s, size_t lg, const wchar_t *from) {
  *s = malloc(sizeof(lgwseq_t));
  (*s)->data = malloc(lg*sizeof(wchar_t));
  wmemcpy((*s)->data, from, lg);
  (*s)->length = lg;
  return *s;
}

size_t pack_string_data(const lgwseq_t *for_pack, void **packed_out) {
  iconv_t cd = iconv_open("UTF-8", "WCHAR_T");
  iconv_t cdutf16 = iconv_open("UTF-16BE", "WCHAR_T");
  char *ibuf = (char *)for_pack->data;
  wchar_t *dataptr;
  wchar_t *endptr = for_pack->data+for_pack->length;
  size_t bufsize = (size_t)((char *)endptr-(char *)for_pack->data);
  size_t obuflen=bufsize;
  char *outbuf = malloc(bufsize), *obuf;
  // Packs the size attribute. The length of uleb128 suits in for packing numbers of up to 32-bit. Larger numbers are supported by the uleb* encoding family but will not be required by any instance of data encoded here.
  uint8_t uleb128[5] = {0x00,0x00,0x00,0x00,0x00};
  int span = 1;
  uint32_t length = for_pack->length;
  int i;
  for(i=0; i!=5; i++) {
    uleb128[i] = (length  >> (i*7)) & 0x7F;
    if(uleb128[i]!=0x00)
      span = i+1;
  }
  for(i=0; i!=span-1; i++)
    uleb128[i] |= 0x80;
  memcpy(outbuf, &uleb128, span);
  obuf = outbuf+span;

  do { // string recomposition loop
    // Get largest segment of straightfoward-representable codepoints (in opposition to surrogatable codepoints)
    dataptr = (wchar_t *)ibuf;
    /* The reason for why a straightfoward formula like dataptr<L'\U00010000' is not in use, steems in a lack of guaranty about the "arithmetic" behaviour of character sets to wide characters.
     * The fear about this lack of guaranty can be reasoned from the following lines taken from GNU C Library Manual Reference
     * http://www.gnu.org/software/libc/manual/html_mono/libc.html#Converting-a-Character (eitherly found at gnu-doc-reference package from Debian Repos)
     * Why is it necessary to use such a complicated implementation and not simply cast '0' + val % 10 to a wide character?
     * The answer is that there is no guarantee that one can perform this kind of arithmetic on the character of the character set used for wchar_t representation.
     * In other situations the bytes are not constant at compile time and so the compiler cannot do the work. In situations like this, using btowc is required. 
     */
    while(dataptr!=endptr && *dataptr!=L'\0'&& wmemcmp(dataptr, L"\U00010000",1)<0)
      dataptr++;
    size_t ibuflen = (char *)dataptr-ibuf;
    do {   // iconv loop
      if(iconv(cd, &ibuf, &ibuflen, &obuf, &obuflen) != (size_t) -1)
        break;
      else if(errno==E2BIG) {  // The buffer is full, make it larger.
        bufsize *= 2;
        ptrdiff_t obufoffset = obuf-outbuf;
        outbuf = realloc(outbuf, bufsize);
        obuf = outbuf+obufoffset;
        obuflen = bufsize-obufoffset;
      }
      else {
        char *str_err = strerror(errno);
        fprintf(stderr, "In function %s, %s reported  from iconv().", __func__, str_err);
        exit(EXIT_FAILURE);
      }
    }while(1);
    if(dataptr==endptr)
      break;
    // Surrogating...
    if(*dataptr==L'\0') {
      if(obuflen<2) {
        bufsize *= 2;
        ptrdiff_t obufoffset = obuf-outbuf;
        outbuf = realloc(outbuf, bufsize);
        obuf = outbuf+obufoffset;
        obuflen = bufsize-obufoffset;
      }
      const uint16_t NULLSURRG = 0xC080;
      memcpy(obuf, &NULLSURRG, 2);
      // Update buffer trackers
      dataptr++;
      ibuf = (char *)dataptr;
      obuf += 2;
      obuflen -= 2;
    }
    else {
      ibuflen = sizeof(wchar_t);
      uint32_t tmpbuf, *tmpbufptr=&tmpbuf;
      size_t tmpbuflen = (32/CHAR_BIT) + 32%CHAR_BIT?1:0;
      
      if(iconv(cdutf16, (char **)&dataptr, &ibuflen, (char **)&tmpbufptr, &tmpbuflen) == (size_t) -1) {
        char *str_err = strerror(errno);
        fprintf(stderr, "In function %s, %s reported from iconv().", __func__, str_err);
        exit(EXIT_FAILURE);
      }
      // Update buffer trackers
      ibuf = (char *)dataptr;
      // Apply bit pattern 110xxxxx 10xxxxxx 10xxxxxx twice, one on each UTF-16 surrogate half codeunit.
      uint32_t halves[2];
      halves[0] = 0xE08080 | ( (tmpbuf & 0xF0000000) >> 12) | ( (tmpbuf & 0x0FC00000) >> 18 ) | ( (tmpbuf & 0x003F0000) >> 24 );
      halves[1] = 0xE08080 | ( (tmpbuf & 0xF000) << 4) | ( (tmpbuf & 0x0FC0) >> 2 ) | ( (tmpbuf & 0x003F) >> 8 );
      // Check buffer limit and write the converted bytes
      if(obuflen<6) {
        bufsize *= 2;
        ptrdiff_t obufoffset = obuf-outbuf;
        outbuf = realloc(outbuf, bufsize);
        obuf = outbuf+obufoffset;
        obuflen = bufsize-obufoffset;
      }
      memcpy(obuf, (char *)halves+1, 3);   // First half. The first byte of the uint32_t is skypped as the 3 low-order bytes have the data.
      memcpy(obuf+3, (char *)(halves+1)+1, 3);   // Second half
      // Update buffer trackers
      obuf += 6;
      obuflen -= 6;
    }
  }while(dataptr!=endptr);
  // Packed string_data_item returning...
  ptrdiff_t offset = obuf-outbuf;
  *packed_out = realloc(outbuf, offset+1); // Reserves +1 position for the final '\0' appended right next
  *((char *)*packed_out+offset)='\0';
  return offset+1;
  /* With wchar_t strings, which length in term of chars can be derived by 
   * simple distance through L'\0' (by means of wcschr function, for example), it deserves to be traded off with the 
   * formula involving multiplication of wchar_t quantity per its unit size. And for the latter case, a certainty 
   * about the constantness of wchar_t's size is needed to validate the formula's correctness.
   * The nuances about all this are turned out with the fact that within sizeof(wchar_t)==n, n cannot be overridden by for example
   * sizeof((wchar_t)L'a'), sizeof((wchar_t)L'\u0580'), sizeof((wchar_t)L'\u0800') or sizeof((wchar_t)L'\U00010000'), despite the fact that a similar function on a variable length encoding like UTF-8, did the function exist, would yield all different sizes for results.
   */
}

int lgwseq_t_compar(const lgwseq_t *lgwseq1, const lgwseq_t *lgwseq2) {
  int cmp = wmemcmp(lgwseq1->data, lgwseq2->data, lgwseq1->length<lgwseq2->length?lgwseq1->length:lgwseq2->length);
  return cmp==0?lgwseq1->length-lgwseq2->length:cmp;
}

lgwseq_t *lgwseq_concat(int n_str, ...) {
  va_list ap;
  va_start(ap, n_str);
  int Length = 0;
  for(int str_no=0; str_no < n_str; str_no++)
    Length += va_arg(ap, lgwseq_t *)->length;
  va_end(ap);

  wchar_t *dst  = malloc(Length*sizeof(wchar_t));

  va_start(ap, n_str); 
  int base=0;
  for(int str_no=0; str_no<n_str, str_no++)
  {
    lgwseq_t *str = va_arg(ap, lgwseq_t *);
    wmemcpy(dst+base, str->data, str->length);
    base += str->length;
  }
  va_end(ap);
  lgwseq_t *cat = malloc(sizeof(lgwseq_t));
  cat-> data = dst;
  cat-> length = base;
  return cat;
}


